/*
 * Copyright 2007 Agile Plugins Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.agileplugins.monkey;

import com.intellij.ide.util.EditSourceUtil;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.DataKeys;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.pom.Navigatable;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;

class AutoBoundaryGotoImplementation extends AnAction {
    private Logger logger = Logger.getInstance(getClass().getName());

    // FIX KEF Make this configurable.
    private static final String AUTOBOUNDARY_PACKAGE_PREFIX = "au.com.suncorp.easydoc.autoboundary.";
    private static final String FACTORY_CLASS_SUFFIX = "Factory";
    private static final String NEW_METHOD_PREFIX = "new";
    private static final boolean REQUEST_FOCUS = true;

    private final AnAction implementationAction;

    public AutoBoundaryGotoImplementation(AnAction implementationAction) {
        this.implementationAction = implementationAction;
    }

    public void actionPerformed(AnActionEvent anactionevent) {
        DataContext dataContext = anactionevent.getDataContext();
        Project project = DataKeys.PROJECT.getData(dataContext);
        PsiElement boundaryElement = DataKeys.PSI_ELEMENT.getData(dataContext);
        actionPerformed(anactionevent, project, boundaryElement);
    }

    private void actionPerformed(AnActionEvent anactionevent, Project project, PsiElement boundaryElement) {
        PsiClass realClass = getRealClass(project, boundaryElement);
        if (realClass == null) {
            implementationAction.actionPerformed(anactionevent);
            return;
        }
        PsiMethod realMethod = getRealMethod(boundaryElement, realClass);
        if (realMethod == null) {
            navigateTo(realClass);
        } else {
            navigateTo(realMethod);
        }
    }

    private PsiClass getRealClass(Project project, PsiElement boundaryElement) {
        PsiClass realClass = null;
        PsiClass boundaryClass = getBoundaryClass(boundaryElement);
        if (boundaryClass != null) {
            String realPackageName = getRealPackageName(boundaryClass);
            if (realPackageName != null) {
                PsiManager psiManager = boundaryClass.getManager();
                String boundaryClassName = boundaryClass.getName();
                realClass = findRealClass(project, psiManager, realPackageName, boundaryClassName);
                if ((realClass == null) && isFactoryClass(boundaryClassName)) {
                    realClass = findRealClass(project, psiManager, realPackageName, getNonFactoryClassName(boundaryClassName));
                }
            }
        }
        return realClass;
    }

    private PsiClass getBoundaryClass(PsiElement boundaryElement) {
        PsiElement classElement = boundaryElement;
        if (boundaryElement instanceof PsiMethod) {
            classElement = boundaryElement.getParent();
        }
        return (classElement instanceof PsiClass) ? (PsiClass) classElement : null;
    }

    private String getRealPackageName(PsiClass boundaryClass) {
        String boundaryPackageName = getBoundaryPackageName(boundaryClass);
        if (isAutoBoundaryPackage(boundaryPackageName)) {
            return getRealPackageName(boundaryPackageName);
        }
        return null;
    }

    private String getBoundaryPackageName(PsiClass boundaryClass) {
        PsiFile containingFile = boundaryClass.getContainingFile();
        if (containingFile instanceof PsiJavaFile) {
            PsiJavaFile psiJavaFile = (PsiJavaFile) containingFile;
            return psiJavaFile.getPackageName();
        }
        return null;
    }

    private PsiClass findRealClass(Project project, PsiManager manager, String realPackageName, String boundaryClassName) {
        String realClassName = realPackageName + "." + boundaryClassName;
        GlobalSearchScope globalsearchscope = GlobalSearchScope.allScope(project);
        return manager.findClass(realClassName, globalsearchscope);
    }

    private PsiMethod getRealMethod(PsiElement boundaryElement, PsiClass realClass) {
        PsiMethod realMethod = null;
        PsiMethod boundaryMethod = getBoundaryMethod(boundaryElement);
        if (boundaryMethod != null) {
            String boundaryMethodName = boundaryMethod.getName();
            realMethod = findRealMethod(realClass, boundaryMethod, boundaryMethodName);
            if (realMethod == null && isNewMethod(boundaryMethodName)) {
                String constructorName = realClass.getName();
                realMethod = findRealMethod(realClass, boundaryMethod, constructorName);
            }
        }
        return realMethod;
    }

    private PsiMethod getBoundaryMethod(PsiElement boundaryElement) {
        return (boundaryElement instanceof PsiMethod) ? (PsiMethod) boundaryElement : null;
    }

    private PsiMethod findRealMethod(PsiClass realClass, PsiMethod boundaryMethod, String methodNameToMatch) {
        PsiMethod[] realMethods = realClass.getMethods();
        for (int i = 0; i < realMethods.length; i++) {
            PsiMethod realMethod = realMethods[i];
            if (methodMatches(realMethod, boundaryMethod, methodNameToMatch)) {
                return realMethod;
            }
        }
        return null;
    }

    private boolean methodMatches(PsiMethod realMethod, PsiMethod boundaryMethod, String methodNameToMatch) {
        return realMethod.getName().equals(methodNameToMatch) &&
                parameterCount(realMethod) == parameterCount(boundaryMethod);
    }

    private int parameterCount(PsiMethod realMethod) {
        return realMethod.getParameterList().getParameters().length;
    }

    private boolean isNewMethod(String methodName) {
        return methodName.startsWith(NEW_METHOD_PREFIX);
    }

    private boolean isAutoBoundaryPackage(String packageName) {
        return (packageName != null) && packageName.startsWith(AUTOBOUNDARY_PACKAGE_PREFIX);
    }

    private String getRealPackageName(String boundaryPackageName) {
        return boundaryPackageName.substring(AUTOBOUNDARY_PACKAGE_PREFIX.length());
    }

    private boolean isFactoryClass(String className) {
        return className.endsWith(FACTORY_CLASS_SUFFIX);
    }

    private String getNonFactoryClassName(String boundaryClassName) {
        return boundaryClassName.substring(0, boundaryClassName.length() - FACTORY_CLASS_SUFFIX.length());
    }

    private void navigateTo(PsiElement navElement) {
        Navigatable navigatable = EditSourceUtil.getDescriptor(navElement);
        if (navigatable != null && navigatable.canNavigate()) {
            navigatable.navigate(REQUEST_FOCUS);
        }
    }

    public boolean displayTextInToolbar() {
        return implementationAction.displayTextInToolbar();
    }

    public void update(AnActionEvent anactionevent) {
        implementationAction.update(anactionevent);
    }

    public void setDefaultIcon(boolean isDefaultIconSet) {
        implementationAction.setDefaultIcon(isDefaultIconSet);
    }

    public boolean isDefaultIcon() {
        return implementationAction.isDefaultIcon();
    }
}
